// Copyright 2022 The Liquigo Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package handle

import (
	"errors"
	"os"
	"path/filepath"
	"strings"

	. "gitee.com/west0207/liquigo/core/log"

	config "gitee.com/west0207/liquigo/core/config"
	db "gitee.com/west0207/liquigo/core/db"

	translate "gitee.com/west0207/liquigo/core/translate"
	utils "gitee.com/west0207/liquigo/core/utils"

	etree "gitee.com/west0207/etree"
)

// 每个changeSet的执行顺序
var executedOrder int = 0

// app.yml配置中的一个db配置
var DbConfig *config.DB

// 空转时导出sql语句的保存文件
var SqlOutPutFile *os.File

// 处理入口
// yamlConfig string app.yml
func Handle(yamlConfig string) error {
	dbConfigs, err := config.GetYamlConfig(yamlConfig)
	if err != nil {
		// 获取app.yaml配置失败
		Sug.Errorf("Get app.yml configuration failed, err: %v", err)
		panic(err)
	}
	for i := 0; i < len(dbConfigs); i++ {
		if err := handleDbConfig(&dbConfigs[i]); err != nil {
			return err
		}
	}
	return nil
}

// 处理yaml配置中的一个db配置
// dbConfig *config.DB yaml配置中的一个db配置
func handleDbConfig(dbConfig *config.DB) error {
	DbConfig = dbConfig
	if config.DryRun {
		for _, entryXml := range DbConfig.EntryXml {
			if err := handleEntryXml(&entryXml); err != nil {
				return err
			}
		}
		return nil
	}
	db.InitDb(DbConfig)
	// if err != nil {
	// 	// 初始化数据库失败
	// 	Sug.Errorf("Database initialization failed, err: %v", err)
	// 	panic(err)
	// }
	// 数据库初始化成功
	Sug.Infof("- Database initialization succeeded: %v", DbConfig.DbmsName)
	defer Sug.Infof("- Database closed successfully: %v", DbConfig.DbmsName)
	defer db.Engine.Close()

	// 创建元数据表，如果不存在的话
	if err := CreateMetaTable(); err != nil {
		// 创建元数据库表失败
		Sug.Errorf("Failed to create meta database table, err: %v", err)
		panic(err)
	}

	executedOrder = SelectMaxExecutedOrder()
	for _, entryXml := range DbConfig.EntryXml {
		if err := handleEntryXml(&entryXml); err != nil {
			return err
		}
	}
	return nil
}

// 处理一个entryXml文件
// dbConn *sql.DB 该数据库连接实例
// entryXml *string 例如：entry-xxxx-app01.xml
func handleEntryXml(entryXml *string) error {
	entryXmlConfig, err := config.GetEntryXmlConfig(entryXml)
	if err != nil {
		// 获取入口XML文件配置失败
		Sug.Errorf("Failed to get entry XML file configuration, entryXml: %v, error: %v", *entryXml, err)
		panic(err)
	}

	Sug.Infof("-- EntryXml: %v", *entryXml)
	for _, include := range entryXmlConfig.Include {
		if err := handleChangeSetXml(entryXml, &include.File); err != nil {
			return err
		}
	}
	return nil
}

// 处理一个changeSetXml文件
// dbConn *sql.DB 该数据库连接实例
// entryXml *string 例如：entry-xxxx-app01.xml
// changeSetFile *string 例如：classpath:db/changelog-base/db-base-sys-ddl.xml
func handleChangeSetXml(entryXml *string, changeSetFile *string) error {
	path := strings.Replace(*changeSetFile, "classpath:", utils.EMPTY, -1)
	Sug.Infof("--- ChangeSetFile %v", path)
	// db/changelog-base/db-base-sys-ddl.xml
	// config.GetChangeSetXmlConfig(path)

	// defer Sug.Infof("defer SqlOutPutFile.Close() done!\n")
	// defer SqlOutPutFile.Close()

	fileSum, err := utils.Sha256File(&path)
	if err != nil {
		// 计算changeSetFile文件的sha256摘要值失败
		Sug.Errorf("Failed to calculate the sha256 summary value of changeSetFile: %v error: %v", path, err)
		panic(err)
	}
	_, fileName := filepath.Split(path)
	// Sug.Infof("fileSum:", fileSum)

	// 暂时不做该控制，将来使用命令行参数控制
	// setFileChanged := CheckDbChangeFileById(dbConn, &fileName, &fileSum)
	// if !setFileChanged {
	// 	// ChangeSetFile无变更，不需要运行
	// 	Sug.Infof("The ChangeSetFile %v has not been changed and does not need to be run", path)
	// 	return
	// }

	changeSets, err := config.GetChangeSets(path)
	if err != nil {
		// 获取changeSetFile文件中的changeSet元素列表失败
		Sug.Errorf("Failed to get the changeSet element list in changeSetFile, changeSetFile: %v error: %v", path, err)
		panic(err)
	}

	if config.DryRun {
		os.MkdirAll("sql", os.ModeDir|os.ModePerm)
		dsnSum := utils.Sha256(&DbConfig.DataSourceName)
		sqlFile := strings.Replace("sql/"+DbConfig.DbmsName+utils.DASH+dsnSum[:8]+ //
			utils.DASH+*entryXml+utils.DASH+fileName+".sql", //
			".xml", utils.EMPTY, -1)

		// 文件打开时，如果文件不存在会创建，如果文件有内容，则清空从头开始写
		SqlOutPutFile, err = os.OpenFile(sqlFile, os.O_CREATE|os.O_RDWR, os.ModeAppend|os.ModePerm)
		if err != nil {
			Sug.Errorf("openFile err: %v, sqlFile: %v", err, sqlFile)
		}
		// defer Sug.Infof("defer SqlOutPutFile.Close() done: %v\n", sqlFile)
		defer SqlOutPutFile.Close()
		SqlOutPutFile.WriteString("-- " + DbConfig.DbmsName + " " + DbConfig.DataSourceName + utils.LF)
		SqlOutPutFile.WriteString("-- " + *entryXml + " " + path + utils.LF)

		Sug.Infof("DryRun sqlFile: %v", sqlFile)
	}
	// sql := utils.EMPTY
	for _, changeSet := range changeSets {
		if err := handleChangeSet(&path, changeSet); err != nil {
			return err
		}
	}
	if config.DryRun {
		return nil
	}
	UpdateDbChangeFileById(&fileName, &fileSum, &path, entryXml)
	return nil
}

// 处理一个changeSet
// dbConn *sql.DB 该数据库连接实例
// changeSetFile *string 例如：classpath:db/changelog-base/db-base-sys-ddl.xml
// changeSetEle *etree.Element 一个changeSet的XML元素，<changeSet id="...">...</changeSet>
func handleChangeSet(changeSetFile *string, changeSetEle *etree.Element) error {
	changeSetId := changeSetEle.SelectAttrValue("id", utils.UNKNOWN)
	author := changeSetEle.SelectAttrValue("author", utils.UNKNOWN)
	runOnChange := changeSetEle.SelectAttrValue("runOnChange", utils.FALSE)
	failOnError := changeSetEle.SelectAttrValue("failOnError", utils.TRUE)
	dbms := changeSetEle.SelectAttrValue("dbms", utils.ALL)
	runAlways := changeSetEle.SelectAttrValue("runAlways", utils.FALSE)
	ignore := changeSetEle.SelectAttrValue("ignore", utils.FALSE)

	Sug.Infof("changeSet id = %v author = %v runOnChange = %v", //
		changeSetId, author, runOnChange)
	// Sug.Infof("changeSet element: id = %v failOnError = %v dbms = %v", //
	// 	changeSetId, failOnError, dbms)
	// Sug.Infof("changeSet element: id = %v runAlways = %v ignore = %v", //
	// 	changeSetId, runAlways, ignore)
	if ignore == utils.TRUE {
		// 配置为忽略
		Sug.Infof("The changeSet(%v) ignore is true", changeSetId)
		return nil
	}
	isHit := translate.CheckDbms(&DbConfig.DbmsName, &dbms)
	if !isHit {
		// 未命中，配置不允许在当前数据库类型上运行脚本
		Sug.Infof("The dbms configuration does not allow scripts to run on %v", DbConfig.DbmsName)
		return nil
	}

	if !config.DryRun { // 非空转
		checkedResult := handlePreConditions(changeSetEle)
		switch checkedResult {
		case utils.EMPTY:
			// 前置条件校验正常且为true，继续执行该changeSet
		case utils.PC_CONTINUE:
			// 1 前置条件校验为false或异常，则跳过该changeSet，下次还会运行
			// 2 数据库中已经存在该记录并且标记为MARK_RAN，则跳过该changeSet
			return nil
		case utils.PC_MARK_RAN:
			// 前置条件校验为false或异常，则标记该changeSet为已运行，后续不会再运行
			InsertDbChangeSetForMarkRun(&changeSetId, &author, changeSetFile, &executedOrder, changeSetEle)
			return nil
		case utils.PC_WARN:
			// 前置条件校验为false或异常，输出警告信息并继续执行该changeSet
			Sug.Error("[ preConditions WARN ] The changeset will continue to run despite sending errors")
		default:
			panic("Unknown preConditions onFail/onError configuration: " + checkedResult)
		}
	}

	entityEles := changeSetEle.ChildElements()
	var builder strings.Builder
	entityLen := len(entityEles)
	builder.Grow(entityLen * utils.ONE_THOUSAND)

	for i := 0; i < entityLen; i++ {
		if entityEles[i].Tag == "stop" {
			message := entityEles[i].SelectAttrValue("message", utils.EMPTY)
			return errors.New("liquigo stopped by <stop> tag: " + message)
		}
		entityEleSql, err := translate.GetSql(DbConfig, entityEles[i])
		if err != nil {
			// 翻译entityEle为sql语句失败
			Sug.Errorf("Failed to translate entityEle into SQL statement, err: %v", err)
			panic(err)
		}
		utils.StringAppender(&builder, entityEleSql)
	}

	changeSetSql := builder.String()
	if config.DryRun {
		// 空转
		_, err := SqlOutPutFile.WriteString(utils.LF + "-- " + utils.LF + "-- " + changeSetId + utils.LF + "-- " + utils.LF + changeSetSql)
		return err
	} else {
		// 写入日志文件
		Sugf.Info("changeSetSql:" + utils.LF + changeSetSql)
	}
	changeSetSum := utils.Sha256(&changeSetSql)

	changeSetStatus, dbChangeSet, err := CheckDbChangeSet(&changeSetId, &runOnChange, &changeSetSum)
	if err != nil {
		// 检查元数据库表dbchangeset的记录失败
		Sug.Errorf("Failed to check records of table dbchangeset, changeSetId: %v, error: %v", changeSetId, err)
		panic(err)
	}
	if runAlways == utils.TRUE {
		changeSetStatus = utils.CS_RUN
	}
	// fmt.Println("changeSetStatus:", changeSetStatus)

	switch changeSetStatus {
	case utils.CS_RUN:
		// 需要执行
		Sug.Infof("Prepare to execute the changeSetId(%v)", changeSetId)
	case utils.CS_EXECUTED:
		// 已经执行过且无变更，无需执行
		Sug.Infof("The changeSetId(%v) has been executed and has not been changed", changeSetId)
		return nil
	case utils.CS_CONFLICT:
		// 有冲突，中断应用
		Sug.Errorf("There is a conflict in this changeSetId(%v)", changeSetId)
		Sug.Errorf("old sha256: %v", dbChangeSet.ChangesetSign)
		Sug.Errorf("new sha256: %v", changeSetSum)
		panic("There is a sha256 conflict in this changeSetId")
	}

	if changeSetSql == utils.EMPTY {
		// 翻译后的sql语句为空串，无需执行
		Sug.Infof("The changeSetId(%v) translated SQL statement is an empty string and does not need to be executed", changeSetId)
		return nil
	}

	// // 开启事务操作
	// dbTx, errTxBegin := dbConn.Begin()
	// if errTxBegin != nil {
	// 	// 开启数据库事务失败，也可能是不支持事务
	// 	Sug.Errorf("Failed to open database transaction, errTxBegin: %v", errTxBegin)
	// 	panic(errTxBegin)
	// }
	session := db.Engine.NewSession()
	defer session.Close()
	// add Begin() before any action
	if errTxBegin := session.Begin(); errTxBegin != nil {
		// 开启数据库事务失败，也可能是不支持事务
		Sug.Errorf("Failed to open database transaction, errTxBegin: %v", errTxBegin)
		panic(errTxBegin)
	}

	if DbConfig.DbmsName == translate.Dameng || DbConfig.DbmsName == translate.Oracle {
		// 达梦和Oracle数据库，不能批量执行sql，只能以分号拆分sql逐个运行
		changeSetSqlArr := strings.Split(changeSetSql, utils.SEMICOLON)
		for _, singleSql := range changeSetSqlArr {
			if strings.Trim(singleSql, utils.BLANK_CHARS) == utils.EMPTY {
				continue
			}
			if _, errTxExec := session.Exec(singleSql); errTxExec != nil {
				// 执行该changeSetId的sql语句失败
				Sug.Errorf("Failed to execute SQL statement of changeSetId(%v), errTxExec: %v", changeSetId, errTxExec)
				Sugf.Error(utils.LF, singleSql)

				// 自动回滚事务
				errTxRollback := session.Rollback()
				if errTxRollback != nil {
					Sug.Errorf("Failed to rollback database transaction, changeSetId(%v), errTxRollback: %v", changeSetId, errTxRollback)
				}
				// 运行rollback标签回滚事务
				if errRollbackTag := handleRollbackTag(changeSetEle); errRollbackTag != nil {
					Sugf.Errorf("Failed to execute the rollback entity tag, changeSetId(%v), errRollbackTag: %v", changeSetId, errRollbackTag)
				}
				if failOnError == utils.TRUE {
					panic(errTxExec)
				}
				Sug.Error("Since failOnError of this changeSet is false, continue to execute")
				return nil
			}
		}
	} else {
		// 非达梦和Oracle数据库，可以批量执行sql
		if _, errTxExec := session.Exec(changeSetSql); errTxExec != nil {
			// 执行该changeSetId的sql语句失败
			Sug.Errorf("Failed to execute SQL statement of changeSetId(%v), errTxExec: %v", changeSetId, errTxExec)
			Sugf.Error(utils.LF, changeSetSql)
			// 自动回滚事务
			errTxRollback := session.Rollback()
			if errTxRollback != nil {
				Sug.Errorf("Failed to rollback database transaction, changeSetId(%v), errTxRollback: %v", changeSetId, errTxRollback)
			}
			// 运行rollback标签回滚事务
			if errRollbackTag := handleRollbackTag(changeSetEle); errRollbackTag != nil {
				Sugf.Errorf("Failed to execute the rollback entity tag, changeSetId(%v), errRollbackTag: %v", changeSetId, errRollbackTag)
			}
			if failOnError == utils.TRUE {
				panic(errTxExec)
			}
			Sug.Error("Since failOnError of this changeSet is false, continue to execute")
			return nil
		}
	}

	InsertOrUpdateDbChangeSet(session, dbChangeSet, &changeSetId, &author, changeSetFile, &executedOrder, &changeSetSum, changeSetEle)

	// 提交事务
	errTxCommit := session.Commit()
	if errTxCommit != nil {
		Sug.Errorf("Failed to commit database transaction, changeSetId(%v), errTxCommit: %v", changeSetId, errTxCommit)
		errTxRollback := session.Rollback()
		if errTxRollback != nil {
			Sug.Errorf("Failed to rollback database transaction, changeSetId(%v), errTxRollback: %v", changeSetId, errTxRollback)
		}
		panic(errTxCommit)
	}
	// 在数据库中运行成功
	Sug.Infof("The changeSetId(%v) was successfully run in the database", changeSetId)
	return nil
}

// 处理该changeSet中的rollback标签
// <rollback></rollback> 可以包含多个，依次执行
func handleRollbackTag(changeSetEle *etree.Element) error {
	rollbackEles := changeSetEle.SelectElements("rollback")
	if len(rollbackEles) == utils.INT_ZERO {
		// 无rollback配置，直接返回
		return nil
	}
	changeSetId := changeSetEle.SelectAttrValue("id", utils.UNKNOWN)
	Sug.Infof("Prepare to execute the rollback of the changeSetId(%v)", changeSetId)

	if err := translate.HandleRollback(DbConfig, rollbackEles); err != nil {
		return err
	}
	return nil
}

// 处理前置条件标签preConditions
// 一个changeSet标签仅允许包含一个preConditions标签
// 如果不存在或者是前置条件判定为true，则允许执行该changeSet
func handlePreConditions(changeSetEle *etree.Element) string {
	changeSetId := changeSetEle.SelectAttrValue("id", utils.UNKNOWN)
	preConditionsEle := changeSetEle.SelectElement("preConditions")
	if preConditionsEle == nil {
		// 不存在则返回true，允许执行
		return utils.EMPTY
	}
	onFail := preConditionsEle.SelectAttrValue("onFail", utils.PC_HALT)
	onError := preConditionsEle.SelectAttrValue("onError", utils.PC_HALT)
	if onFail == utils.PC_MARK_RAN || onError == utils.PC_MARK_RAN {
		// MARK_RAN
		dbChangeSet, _ := SelectDbChangeSetById(&changeSetId)
		if dbChangeSet != nil {
			if utils.PC_MARK_RAN == dbChangeSet.ExecutedStatus {
				// 数据库中已经存在该记录并且标记为MARK_RAN，则跳过该changeSet
				return utils.PC_CONTINUE
			}
		}
	}

	resultBool, err := translate.CheckPreConditions(DbConfig, preConditionsEle)
	Sug.Infof("changeSet id = %v preConditions = %v onFail = %v", //
		changeSetId, resultBool, onFail)
	if err != nil {
		// 根据onError配置执行
		return onError
	}
	if resultBool {
		return utils.EMPTY
	}
	// 根据onFail配置执行
	return onFail
}
